#include "AzRecord.h"

#include <QDebug>

#include "AzField.h"
#include "AzRootRecord.h"






/*!
	\class AzRecord
	Класс отображающий запись БД.
    Предназначен для хранения данных записи с SQL сервера (и не только)
    ТЗ
    1. Класс максимально открыт, но без фанатизма
    2. Класс не безопасен - количество проверок ограничено.
    3. Максимально уменьшить кол-во методов класса, но не за счет усложнения.
    4. Чёткая и однозначная документация из за небезопасности класса.
    5. Объекты класса должны всегда! иметь указатель на родительский объект
*/

/*!
	\enum AzRecord::RecordState
	\brief Определяет состояние записи по отношению к серверу.

	\var AzRecord::rsUnmodified
	\brief Запись не изменена на клиенте

	\var AzRecord::rsInserted
	\brief Запись вставлена и не передана на сервер.

	\var AzRecord::rsModified
	\brief Запись  изменена и не передана на сервер.

	\var AzRecord::rsDeleted
	\brief Запись  удалена и не передана на сервер.

*/

/*!
	\enum AzRecord::FilteredState
	\brief Определяет состояние отфильтрованых записей.

	\var AzRecord::fsUnfiltered
	\brief Запись не отфильтрована

	\var AzRecord::fsDeleted
	\brief Запись отфильтрована -  для дальнейшего удаления с сервера.

	\var AzRecord:: fsUser
	\brief Запись отфильтрована - запись отфильтрована пользователем.
*/



/*!
	\fn AzRecord::data (int i)
	\a i - номер колонки.
	\return текущее значение колонки.
*/

/*!
	\fn const VarVector* AzRecord::dataPtr()
	\brief 	\return Указатель на данные записи  - QVector <QVariant>
*/

/*!
	\fn AzRecord::oldData(int)
	\brief Возвращает старое значение данных. Если старое значение не изменилось, возвращает текущее.
*/

/*!
	\fn AzRecord *root() const
	Возвращает корневой элемент записи.
*/

/*!
	\fn const VarVector& AzRecord::data() const
	Return data of the record
*/

//--------------------------------------------------------AzRecordData-------------------------------------------------------------------
/*!
    \class AzRecordData
    \brief Содержит данные записи

    ТЗ.
    Предназначен для хранения данных о записях, в том числе об изменённых.

    = Реализация =
    Содержит данные о актуальных и старых данных. Для простототы размер массива для старых и новых данных данных всегда одинаковен.
    Старые данные, если их не существует, содержат тип QVariant::Invalid
*/


/*!

*/



//--------------------------------------------------------AzRecordException-----------------------------------------

AzRecordException::AzRecordException(Error err): AzException(),mErrCode(err){
    mMessage = codeToStr(err);
}

QString AzRecordException::codeToStr(Error err) {
    switch (err) {
    case RecordOutOfRangeError:     return "Out of ragne record";
    case RecordDataNotAccording:    return "Record data not according fields type";
    case EmptyRecordDataError:      return "Record data must have value";
    case RecordHasParentError:      return "Record has parent";
    case ChangeMetadataError:       return "Can't change metadata, has parent or child data";
    default: break;
    }
    return QString();
}

//------------------------------------------------------AzRecord--------------------------------------------------------

/*!
	Конструктор, где \a size-  размер записи.
	\a state - состояние записи - rsUnmodified - оригинальная запись с сервера, rsInserted - вставлена пользователем.
    \a type - тип записи
*/
AzRecord::AzRecord(RecordType type):mParent(0),mRecordType(type) {
}

AzRecord::AzRecord(const AzRecord& src):mRecordData(src.mRecordData) {

}

AzRecord::~AzRecord(){
    qDeleteAll(mClientRec);
}


/*!
	Устанавливает новое значение. Перемещает старое - \a oldData().
*/
void AzRecord::setData(int i,const QVariant& val) {
    if (recordState() == Az::Unmodified) { //если запись не была модифицирована
        mRecordData.setOldData(mRecordData.data());
        mRecordData.setRecordState(Az::Modified);		//помечаем как измененную
	}
    mRecordData.setData(i,val);
}



/*!
	Возвращает корневую запись
    \Warning Корневая запись должна сущестовать
*/
AzRootRecord* AzRecord::rootRec() {
    return mParent ? (AzRootRecord*)mParent->rootRec() : (AzRootRecord*)this;
}

const AzRootRecord* AzRecord::rootRec() const {
    return mParent ? (const AzRootRecord*)mParent->rootRec() : (const AzRootRecord*)this;
}


/*!
    Return child number current record for parent
*/
int AzRecord::childNumber() const {
    if (mParent)
        return mParent->mClientRec.indexOf((AzRecord*)this);
    return -1;
}


/*!
 *  Проверяет, что индекс \a pos входит в границы диапазона существующих записей.
 *  \return true, если индекс существует.
 */
bool AzRecord::hasChaild(int pos) {
    return pos > -1 && pos < childCount();
}

/*!
 * \brief AzRecord::level
 * \return level
 */
int AzRecord::level() const {
    int i = 0;
    const AzRecord* rec = this;
    while (rec->mParent) {
        ++i;
        rec = rec->mParent;
    }
    return i;
}

/*!
 * \brief AzRecord::insert
 * \return Запись
 * Добавляет пустую запись в позиции \a pos
 * \sa insert(AzRecord*, int )
 */
AzRecord* AzRecord::insert(int pos) {
    const AzRecordData recData;
    return insert(pos,recData);
}


/*!
    Вставляет запись с позиции pos и заполняет её данными \a data. Если \a data пустой, то заполняет
    запись пустыми значениями с типом полей
*/
AzRecord* AzRecord::insert(int pos,const AzRecordData& data) {
    if (pos < 0 && pos > AzRecord::childCount())
        throw AzRecordException(AzRecordException::RecordOutOfRangeError);

    const AzFieldList &fl = rootRec()->fields();
    if (data != fl) //проверка на соответствии данных метаданным
        throw AzRecordException(AzRecordException::RecordDataNotAccording);

    //если data пустая, создаем пустые записи с типом из метаданных
    AzRecord *rec = data.size() == 0 ? AzRecord::createRecord(AzRecord::DataRecord,rootRec()->fields()) : AzRecord::createRecord(AzRecord::DataRecord,data);
    try {
        insert(rec,pos);
    }
    catch (...) {
        delete rec;
        throw;
    }
    return rec;
}


/*!
 * \brief Добавляет существующую запись
 * \param rec - добавляемая запись
 * \param pos - позиция записи
 * Размер записи должен быть одинаковый с родительской.
 * \Warning запись не должна иметь родителя
 * \Note Не стоит пользоваться данным методом, предназначен для загрузки данных, используйте \sa insert(int)
 */
void AzRecord::insert(AzRecord* rec, int pos) {
    if (rec == 0)
        return;
    if (rec->parent())
        throw AzRecordException(AzRecordException::RecordHasParentError);
    rec->mParent = this;
    mClientRec.insert(pos,rec);
}


/*!
    Append record
    \Warning values must be same count as column count.
*/
AzRecord* AzRecord::append() {
    return insert(AzRecord::childCount());
}

/*!
    Удаляет всех потомков и освобождает ресурсы.
*/
void AzRecord::deleteAll() {
    qDeleteAll(mClientRec);
    mClientRec.clear();
}

/*!
 *  Удаляет запись \a row.
 *  В случае неудачи вызывает исключение.
 */
void AzRecord::remove(int row) {
    Q_ASSERT_X(hasChaild(row),"AzTreeRecord::remove()","Out of range records.");
    AzRecord *rec = mClientRec[row];
    delete rec;
    mClientRec.remove(row);
}


QString AzRecord::recordStateString(Az::RecordState state) {
    switch (state) {
        case Az::Unmodified: return "Unmodified";
        case Az::Inserted: return "Inserted";
        case Az::Modified: return "Modified";
        case Az::Deleted: return "Deleted";
    }
    return QString();
}

/*!
 * \brief Создает запись без владельца. За освобождение ресурсов должна отвечать вызывающая сторона
 * \param [in] recordType - Устанавливает тип записи
 * \param [in] data - Устанавливает текущие данные записи
 *
 * \return объект.
 */
AzRecord * AzRecord::createRecord(RecordType recordType,const AzRecordData& data) {
    if (data.size() == 0)
        throw AzRecordException(AzRecordException::EmptyRecordDataError);
    AzRecord *rec = new AzRecord(recordType);
    rec->mRecordData = data;
    return rec;
}


/*!
 * \brief AzRecord::insertColumn вставляет столбец
 * \param i С какой позиции будет вставлятся
 * \param dataType тип данных
 * Вызывает исключение, если есть потомки или родители
 */
void AzRecord::insertColumn(int i, QVariant::Type dataType) {
    checkIsEnableChangeColumn();
    mRecordData.insert(i,dataType);
}

/*!
 * \brief AzRecord::checkIsEnableChangeColumn Вызывает исключение, если невозможно изменить столбцы
 * Проверяет, можно ли изменить столбцы. Должно соблюдаться 2 условия - отсутствие потомков и родителей
 */
void AzRecord::checkIsEnableChangeColumn() const {
    if (mParent || mClientRec.size() > 0)
        throw AzRecordException(AzRecordException::ChangeMetadataError);
}

/*!
 * \brief AzRecord::hasModified
 * \return true, если потомок имеет изменённые записи (на любом уровне)
 */
bool AzRecord::hasModified() const {
    for (int i = 0; i < mClientRec.size();++i) {
        if (mClientRec[i]->recordState() != Az::Unmodified)
            return true;
        if (mClientRec[i]->hasModified())
            return true;
    }
    return false;
}

/*!
 * \brief AzRecord::hasUnmodified
 * \return true, если потомки имеют немодифицированную запись
 */
bool AzRecord::hasUnmodified() const {
    for (int i = 0; i < mClientRec.size();++i) {
        if (mClientRec[i]->recordState() == Az::Unmodified)
            return true;
        if (mClientRec[i]->hasUnmodified())
            return true;
    }
    return false;

}


